#ifndef NODE_DETAIL_NODE_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define NODE_DETAIL_NODE_H_62B23520_7C8E_11DE_8A39_0800200C9A66

#if defined(_MSC_VER) ||                                            \
    (defined(__GNUC__) && (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || \
     (__GNUC__ >= 4)) // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif

#include "yaml-cpp/dll.h"
#include "yaml-cpp/emitterstyle.h"
#include "yaml-cpp/node/detail/node_ref.h"
#include "yaml-cpp/node/ptr.h"
#include "yaml-cpp/node/type.h"
#include <atomic>
#include <set>

namespace YAML {
namespace detail {
class node {
private:
    struct less {
        bool operator()(const node* l, const node* r) const {
            return l->m_index < r->m_index;
        }
    };

public:
    node() : m_pRef(new node_ref), m_dependencies{}, m_index{} {}
    node(const node&) = delete;
    node& operator=(const node&) = delete;

    bool is(const node& rhs) const { return m_pRef == rhs.m_pRef; }
    const node_ref* ref() const { return m_pRef.get(); }

    bool is_defined() const { return m_pRef->is_defined(); }
    const Mark& mark() const { return m_pRef->mark(); }
    NodeType::value type() const { return m_pRef->type(); }

    const std::string& scalar() const { return m_pRef->scalar(); }
    const std::string& tag() const { return m_pRef->tag(); }
    EmitterStyle::value style() const { return m_pRef->style(); }

    template <typename T>
    bool equals(const T& rhs, shared_memory_holder pMemory);
    bool equals(const char* rhs, shared_memory_holder pMemory);

    void mark_defined() {
        if (is_defined())
            return;

        m_pRef->mark_defined();
        for (node* dependency : m_dependencies)
            dependency->mark_defined();
        m_dependencies.clear();
    }

    void add_dependency(node& rhs) {
        if (is_defined())
            rhs.mark_defined();
        else
            m_dependencies.insert(&rhs);
    }

    void set_ref(const node& rhs) {
        if (rhs.is_defined())
            mark_defined();
        m_pRef = rhs.m_pRef;
    }
    void set_data(const node& rhs) {
        if (rhs.is_defined())
            mark_defined();
        m_pRef->set_data(*rhs.m_pRef);
    }

    void set_mark(const Mark& mark) { m_pRef->set_mark(mark); }

    void set_type(NodeType::value type) {
        if (type != NodeType::Undefined)
            mark_defined();
        m_pRef->set_type(type);
    }
    void set_null() {
        mark_defined();
        m_pRef->set_null();
    }
    void set_scalar(const std::string& scalar) {
        mark_defined();
        m_pRef->set_scalar(scalar);
    }
    void set_tag(const std::string& tag) {
        mark_defined();
        m_pRef->set_tag(tag);
    }

    // style
    void set_style(EmitterStyle::value style) {
        mark_defined();
        m_pRef->set_style(style);
    }

    // size/iterator
    std::size_t size() const { return m_pRef->size(); }

    const_node_iterator begin() const {
        return static_cast<const node_ref&>(*m_pRef).begin();
    }
    node_iterator begin() { return m_pRef->begin(); }

    const_node_iterator end() const {
        return static_cast<const node_ref&>(*m_pRef).end();
    }
    node_iterator end() { return m_pRef->end(); }

    // sequence
    void push_back(node& input, shared_memory_holder pMemory) {
        m_pRef->push_back(input, pMemory);
        input.add_dependency(*this);
        m_index = m_amount.fetch_add(1);
    }
    void insert(node& key, node& value, shared_memory_holder pMemory) {
        m_pRef->insert(key, value, pMemory);
        key.add_dependency(*this);
        value.add_dependency(*this);
    }

    // indexing
    template <typename Key>
    node* get(const Key& key, shared_memory_holder pMemory) const {
        // NOTE: this returns a non-const node so that the top-level Node can
        // wrap it, and returns a pointer so that it can be nullptr (if there is
        // no such key).
        return static_cast<const node_ref&>(*m_pRef).get(key, pMemory);
    }
    template <typename Key>
    node& get(const Key& key, shared_memory_holder pMemory) {
        node& value = m_pRef->get(key, pMemory);
        value.add_dependency(*this);
        return value;
    }
    template <typename Key>
    bool remove(const Key& key, shared_memory_holder pMemory) {
        return m_pRef->remove(key, pMemory);
    }

    node* get(node& key, shared_memory_holder pMemory) const {
        // NOTE: this returns a non-const node so that the top-level Node can
        // wrap it, and returns a pointer so that it can be nullptr (if there is
        // no such key).
        return static_cast<const node_ref&>(*m_pRef).get(key, pMemory);
    }
    node& get(node& key, shared_memory_holder pMemory) {
        node& value = m_pRef->get(key, pMemory);
        key.add_dependency(*this);
        value.add_dependency(*this);
        return value;
    }
    bool remove(node& key, shared_memory_holder pMemory) {
        return m_pRef->remove(key, pMemory);
    }

    // map
    template <typename Key, typename Value>
    void force_insert(const Key& key, const Value& value,
                      shared_memory_holder pMemory) {
        m_pRef->force_insert(key, value, pMemory);
    }

private:
    shared_node_ref m_pRef;
    using nodes = std::set<node*, less>;
    nodes m_dependencies;
    size_t m_index;
    static std::atomic<size_t> m_amount;
};
} // namespace detail
} // namespace YAML

#endif // NODE_DETAIL_NODE_H_62B23520_7C8E_11DE_8A39_0800200C9A66
